using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Configuration;
using System.IO;
using Sio.Mdm.Utils;
using System.Diagnostics;

namespace Sio.Mdm.Server
{
    /// <summary>
    /// Class for parsing <see cref="Server"/> confgiration. Class is
    /// static because this parsing should be done once per instance.
    /// </summary>
    public static class Config
    {
        #region Construction
        static Config()
        {
            //FileSystemWatcher watcher = new FileSystemWatcher(ConfigPath);
            //watcher.Changed += delegate(object sender, FileSystemEventArgs e)
            //{
            //    _ignoredInterfaces = null;
            //    _name = null;
            //    _port = 666;
            //    _commandLine = null;
            //    _services = null;                
            //    ParseConfiguration();
            //};
            
            //watcher.Filter = Path.GetFileName(ConfigPath);
            //watcher.Path = Path.GetDirectoryName(ConfigPath);
            //watcher.NotifyFilter = NotifyFilters.LastWrite;
            //watcher.EnableRaisingEvents = true;
        }        
        #endregion

        #region Properties
        
        #region Public

        #region Name
        internal static string _name = "Sio.Mdm.Server";
        /// <summary>
        /// Get name of the server
        /// </summary>
        public static string Name
        {
            get
            {   
                return _name;
            }
        }
        #endregion

        #region Port
        internal static int _port = 666;
        /// <summary>
        /// Optionaly port can be set
        /// </summary>
        public static int Port
        {
            get
            {
                return _port;
            }
        }
        #endregion

        #region CommandLine
        private static CommandLineHelper _commandLine = null;
        /// <summary>
        /// Get command line arguments parsed and paired with values
        /// </summary>
        /// <seealso cref="CommandLineHelper"/>
        public static CommandLineHelper CommandLine
        {
            get
            {
                if (null == _commandLine)
                {
                    string []args= Process.GetCurrentProcess().StartInfo.Arguments.Split(' ');
                    _commandLine = new CommandLineHelper(args);
                }

                return _commandLine;
            }            
        }
        #endregion

        #region IsRemoting
        private static bool _isRemoting = false;
        /// <summary>
        /// Get wheater remoting should be run in remoting mode. Defaults to true.
        /// </summary>
        public static bool IsRemoting
        {
            get
            {
                return _isRemoting;
            }
        }
        #endregion

        #region CollectTimeout
        private static int _collectTimeout = 15;
        /// <summary>
        /// Time after all services requested till then are disposed.
        /// This is very important in long run since based on remoting
        /// time dispose on service is never called and we can not be
        /// sure programmers will do that too.
        /// </summary>
        public static int CollectTimeout
        {
            get
            {
                return _collectTimeout;
            }
        }
        #endregion

        #endregion

        #region Internal

        #region IgnoredInterfaces
        private static List<string> _ignoredInterfaces;
        /// <summary>
        /// Get list of interfaces that should be ignored when creating services from type
        /// </summary>
        internal static IList<string> IgnoredInterfaces
        {
            get
            {
                if (null == _ignoredInterfaces)
                {
                    _ignoredInterfaces = new List<string>();

                    if (File.Exists(ConfigPath))
                    {
                        ParseConfiguration();
                    }
                    
                    _ignoredInterfaces.Add("System.IDisposable");
                    _ignoredInterfaces.Add("Sio.Mdm.Server.IService");
                    _ignoredInterfaces.Add("Sio.Mdm.Server.IRequireServerInstance");
                }

                return _ignoredInterfaces.AsReadOnly();
            }
        }
        #endregion

        #region Services
        private static List<ServiceInformation> _services;
        /// <summary>
        /// Get list of registered services
        /// </summary>
        public static List<ServiceInformation> Services
        {
            get
            {
                if (null == _services)
                {
                    if (File.Exists(ConfigPath))
                    {
                        ParseConfiguration();
                    }
                    else
                    {
                        _services = new List<ServiceInformation>();
                    }
                }

                return _services;
            }
        }
        #endregion  

        #region Commands
        private static List<string> _commands;
        internal static List<string> Commands
        {
            get
            {
                if(null==_commands)
                {
                    _commands = new List<string>();
                }

                return _commands;
            }
        }
        #endregion

        #region ConfigPath
        private static string _configPath;
        internal static string ConfigPath
        {
            get
            {
                if (String.IsNullOrEmpty(_configPath))
                {
                    string path = ConfigurationManager.AppSettings["server.config"];

                    if (!String.IsNullOrEmpty(path) && !Path.IsPathRooted(path))
                    {
                        path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path);
                    }

                    _configPath = path;
                }

                return String.IsNullOrEmpty(_configPath) ? "server.config" : _configPath;
            }
            set
            {
                _configPath = value;
            }
        }
        #endregion

        #region IsAdminEnabled
        internal static bool IsAdminEnabled;
        internal static int AdminPort = 510;
        #endregion

        #region RemoteUser, RemotePassword
        private static string _remoteUser;
        internal static string RemoteUser
        {
            get
            {
                return _remoteUser;
            }
            set
            {
                _remoteUser = value;
            }
        }
        private static string _remotePassword;
        internal static string RemotePassword
        {
            get
            {
                return _remotePassword;
            }
            set
            {
                _remotePassword = value;
            }
        }
        #endregion

        #endregion
        
        #endregion

        #region Methods

        #region Public

        #region Load
        public static void Load()
        {
            ParseConfiguration();            
        }
        public static void Load(string path)
        {
            try
            {
                if (ConfigurationManager.AppSettings["server.config"] == null)
                {
                    ConfigurationManager.AppSettings.Add("server.config", path);
                }
                else
                {
                    ConfigurationManager.AppSettings["server.config"] = path;
                }
            }
            catch
            {
                ConfigPath = path;
            }

            ParseConfiguration();
        } 
        #endregion

        #region Save
        public static void Save()
        {
            Save(ConfigPath);
        }
        public static void Save(string path)
        {
            using (TextWriter writer= File.CreateText(path))
            {
                writer.Write(ToXml());
            }
        }
        #endregion

        #region ToXml
        private static string ToXml()
        {
            StringBuilder builder = new StringBuilder();

            builder.Append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
            builder.Append("<configuration>\n");
            builder.AppendFormat
                (
                "\t<parameters name=\"{0}\" port=\"{1}\" remoting=\"{2}\" collectTimeout=\"{3}\" />\n",
                Name, Port, IsRemoting, CollectTimeout
                );

            builder.Append("<administration enabled=\"true\">");
            builder.AppendFormat("\t<authorization user=\"{0}\" password=\"{1}\" />", Config.RemoteUser, Config.RemotePassword);
            builder.Append("</administration>");

            builder.Append("\t<ignoredInterfaces>\n");
            foreach (string interfaceName in IgnoredInterfaces)
            {
                builder.AppendFormat("\t\t<type name=\"{0}\" />\n", interfaceName);
            }
            builder.Append("\t</ignoredInterfaces>\n");

            builder.Append(Logger.GetXMLConfig());

            builder.Append("\t<services>\n");
            foreach (ServiceInformation service in Services)
            {
                builder.Append(service.ToXML());
                builder.Append("\n");
            }
            builder.Append("\t</services>\n");

            builder.Append("</configuration>\n");

            return builder.ToString();
        }
        #endregion

        #endregion
        
        #region Private

        #region ParseConfiguration
        /// <summary>
        /// Serach for 'server.confg' file and parse configuration for server from it
        /// </summary>
        private static void ParseConfiguration()
        {
            List<ServiceInformation> temporaryServices = new List<ServiceInformation>();

            // TODO [Jure]: Optimize nodes parsing

            try
            {
                XmlDocument doc = new XmlDocument();

                doc.Load(ConfigPath);

                XmlNode configNode = ConfigHelper.GetNodeByName(doc, "configuration");

                if ( configNode == null )
                {
                    throw new ArgumentException("Server configuration XML is malformed! Missing configuration node.");
                }

                // Configuration for all interfaces that should be ignored when
                // service is created from type
                XmlNode ignoredInterfaces = ConfigHelper.GetNodeByName(configNode, "ignoredInterfaces");

                if (ignoredInterfaces != null)
                {
                    _ignoredInterfaces = new List<string>();

                    foreach (XmlNode node in ignoredInterfaces.ChildNodes)
                    {
                        if (node is XmlComment)
                        {
                            continue;
                        }

                        if (String.Compare(node.Name, "type", true) == 0)
                        {
                            string typeName = ConfigHelper.GetAttributeValueByName(node, "name");

                            if (!String.IsNullOrEmpty(typeName))
                            {
                                _ignoredInterfaces.Add(typeName);
                            }
                        }                        
                    }
                }

                // Settings for remoting
                XmlNode parametersNode = ConfigHelper.GetNodeByName(configNode, "parameters");

                if (parametersNode != null)
                {
                    _name = ConfigHelper.GetAttributeValueByName(parametersNode, "name");

                    string port = ConfigHelper.GetAttributeValueByName(parametersNode, "port");

                    if (!String.IsNullOrEmpty(port))
                    {
                        if (!Int32.TryParse(port, out _port))
                        {
                            _port = 666;
                        }
                    }

                    string isRemoting = ConfigHelper.GetAttributeValueByName(parametersNode, "remoting");

                    if (!String.IsNullOrEmpty(isRemoting))
                    {
                        if (!bool.TryParse(isRemoting, out _isRemoting))
                        {
                            _isRemoting = true;
                        }
                    }

                    string collectTimeout = ConfigHelper.GetAttributeValueByName(parametersNode, "collectTimeout");

                    if (!String.IsNullOrEmpty(collectTimeout))
                    {
                        if (!Int32.TryParse(collectTimeout, out _collectTimeout))
                        {
                            _collectTimeout = 15;
                        }
                    }
                }

                // Administrative interface
                XmlNode adminNode = ConfigHelper.GetNodeByName(configNode, "administration");

                if (adminNode != null)
                {
                    string enabled = ConfigHelper.GetAttributeValueByName(adminNode, "enabled");
                    AdminPort = ConfigHelper.GetAttributeValueByNameInt(adminNode, "port");

                    // Should we allow remote administration
                    if (!String.IsNullOrEmpty(enabled))
                    {
                        bool.TryParse(enabled, out IsAdminEnabled);
                    }

                    XmlNode auth = ConfigHelper.GetNodeByName(adminNode, "authorization");

                    if (auth != null)
                    {
                        _remoteUser = ConfigHelper.GetAttributeValueByName(auth, "user");
                        _remotePassword = ConfigHelper.GetAttributeValueByName(auth, "password");
                    }

                    XmlNode commands = ConfigHelper.GetNodeByName(adminNode, "commands");

                    foreach (XmlNode cmd in commands.ChildNodes)
                    {
                        if (cmd.NodeType == XmlNodeType.Comment)
                        {
                            continue;
                        }

                        string typeName = ConfigHelper.GetAttributeValueByName(cmd, "serviceName");

                        if (!String.IsNullOrEmpty(typeName))
                        {
                            Commands.Add(typeName);
                        }
                    }
                }

                XmlNode serviceNode = ConfigHelper.GetNodeByName(configNode, "services");

                foreach (XmlNode node in serviceNode.ChildNodes)
                {
                    // Skip comments
                    if (node is XmlComment)
                    {
                        continue;
                    }

                    ServiceInformation service = ServiceInformation.FromXML(node.OuterXml);

                    if (service.IsFromDisk && !File.Exists(service.Address))
                    {
                        Logger.WriteWarning("Service implementation could not be found ({0})", service.Address);
                    }
                    
                    // Get service from XML
                    temporaryServices.Add(service);
                }

                XmlNode loggingNode = ConfigHelper.GetNodeByName(configNode, "logging");

                if (loggingNode != null)
                {
                    Logger.ConfigureFromXML(loggingNode.OuterXml);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Error parsing configuration! See inner excpetion for details.", ex);
            }

            _services = new List<ServiceInformation>(temporaryServices);            
        } 
        #endregion

        #endregion

        #region Internal

        #region IsInterfaceIgnored
        /// <summary>
        /// Test wheater specified interface should be ignored
        /// </summary>
        /// <param name="interfaceName"></param>
        /// <returns></returns>
        internal static bool IsInterfaceIgnored(string interfaceName)
        {
            foreach (string ignored in IgnoredInterfaces)
            {
                if (interfaceName.Contains(ignored)) // String.Compare(ignored, interfaceName, true) == 0)
                {
                    return true;
                }
            }
    
            return false;
        }
        #endregion

        #region ParseCommandLine
        internal static void ParseCommandLine(string[] args)
        {
            _commandLine = new CommandLineHelper(args);
        }
        #endregion

        #region AddServiceInternal
        internal static void AddServiceInternal(ServiceInformation service)
        {
            if (_services == null)
            {
                _services = new List<ServiceInformation>();
            }

            _services.Add(service);
        }
        #endregion

        #endregion

        #endregion
    }
}
